package com.appengine.paranoid_android.lost;

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.preference.Preference.OnPreferenceClickListener;
import android.provider.Contacts;
import android.provider.Contacts.ContactMethods;
import android.provider.Contacts.People;
import android.provider.Contacts.Phones;
import android.util.Log;
import android.view.Gravity;
import android.widget.ListAdapter;

public class InfoSetup extends PreferenceActivity {
  // Debug and debugging behavior control,
  private static final boolean DEBUG = false;
  private static final String TAG = "Info.Setup";
  private static final boolean CLEAR_PREFERENCES = false && DEBUG;

  // Request code for startActivityForResult().
  private static final int REQUEST_CODE_PICK_CONTACT = 13;

  // Main screen and contents.
  public static final String TEXT_SOURCE_KEY = "text_source";
  public static final String TEXT_BEFORE_KEY = "text_before";
  public static final String TEXT_MESSAGE_KEY = "text_message";
  public static final String MESSAGE_KEY = "message";
  public static final String TEXT_AFTER_KEY = "text_after";
  // Message preview/informational message.
  private static final String PREVIEW_GROUP = "preview_group";
  private static final String MESSAGE_PREVIEW_KEY = "message_preview";

  // Contact screen and contents.
  private static final String CONTACT_SCREEN = "contact_screen";
  private static final String CONTACT_KEY = "contact";
  // Contact methods.
  private static final String CONTACT_METHODS_GROUP = "contact_methods_group";
  private static final String PHONE_KEY_PREFIX = "phone_";
  private static final String CONTACT_METHOD_KEY_PREFIX = "contact_method_";

  private static final int CRASH_WARNING_DIALOG = 130;

  private PreferenceScreen mMainScreen;
  private EditTextPreference mTextBefore;
  private EditTextPreference mTextMessage;
  private EditTextPreference mTextAfter;
  private PreferenceCategory mPreviewGroup;
  private LongTextPreference mMessagePreview;
  private ListPreference mTextSource;

  private PreferenceScreen mContactScreen;
  private Preference mContact;
  private PreferenceCategory mContactMethodsGroup;

  private SharedPreferences mPreferences;
  private PreferenceChangeListener mPreferenceChangeListener;

  /** Disable spurious message updates while (re)building the preference screen. */
  private boolean mDisableMessageUpdates;

  /** No contact selected yet. */
  private boolean mNoContact;
  private boolean mNoTextMessage;

  /** Updates the message when the text before/after is changed or a contact method is checked/unchecked. */
  class PreferenceChangeListener implements SharedPreferences.OnSharedPreferenceChangeListener {
    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
      if (DEBUG) Log.d(TAG, "Preference changed: " + key);
      if (mDisableMessageUpdates) {
        // The screen is getting rebuilt. Don't update the info message.
        return;
      }

      if (TEXT_BEFORE_KEY.equals(key) || 
        TEXT_AFTER_KEY.equals(key) ||
        TEXT_MESSAGE_KEY.equals(key) ||
        TEXT_SOURCE_KEY.equals(key) ||
        key.startsWith(PHONE_KEY_PREFIX) || 
        key.startsWith(CONTACT_METHOD_KEY_PREFIX)) {
        String message = buildInfoMessage();
        if (message != null) {
          setInfoMessage(message, null);
        }
      }
      
      togglePreferenceVisibility();

    }
  }
  
  /**
   * Enable/disable the contact selection or direct text entry preference widgets based on the state of the selected source for the content
   */
  private void togglePreferenceVisibility() {
    if (mTextSource != null  && mTextMessage != null && mContactScreen != null) {
    	if (!useContactSource()) {
        mContactScreen.setEnabled(false);
        mTextMessage.setEnabled(true);
      } else {
        mContactScreen.setEnabled(true);
        mTextMessage.setEnabled(false);
      }
    }

    String contact = mPreferences.getString(CONTACT_KEY, null);
    String textMessage = mPreferences.getString(TEXT_MESSAGE_KEY, null);
    mNoContact = contact == null;
    mNoTextMessage = textMessage == null;
    
    // We need either a contact or direct text input before we can have before/after
    mTextBefore.setEnabled(!mNoContact || !mNoTextMessage);
    mTextAfter.setEnabled(!mNoContact  || !mNoTextMessage);
    
  }
  
  /**
   * Did the user select the "Contact" as a source?
   * defaults to True
   * is false if they chose the "Text" source
   */
  private boolean useContactSource() {
    if (mTextSource != null) {
      String textSource = mTextSource.getValue();
      if (textSource != null && textSource.equals("Text")) {
      	return false;
      }
    }
    return true;
  }
 
  /**
   * Load the preference hierarchy, making the necessary updates to reflect the current status, get references to all
   * the interesting preferences, register click and change listeners and start the {@link InfoService}.
   */
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Retrieve the app's default SharedPreferences.
    mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
    if (DEBUG) {
      if (CLEAR_PREFERENCES) {
        clearPreferences();
      } else {
        Log.d(TAG, "Text before (1): " + mPreferences.getString(TEXT_BEFORE_KEY, null));
      }
    }

    addPreferencesFromResource(R.xml.setup);
    mMainScreen = getPreferenceScreen();
    mTextSource = (ListPreference) findPreference(TEXT_SOURCE_KEY);
    mTextBefore = (EditTextPreference) findPreference(TEXT_BEFORE_KEY);
    mTextAfter = (EditTextPreference) findPreference(TEXT_AFTER_KEY);
    mTextMessage = (EditTextPreference) findPreference(TEXT_MESSAGE_KEY);
    mPreviewGroup = (PreferenceCategory) findPreference(PREVIEW_GROUP);
    mMessagePreview = (LongTextPreference) findPreference(MESSAGE_PREVIEW_KEY);

    mContactScreen = (PreferenceScreen) findPreference(CONTACT_SCREEN);
    mContactScreen.setOnPreferenceClickListener(new OnPreferenceClickListener() {
      @Override
      public boolean onPreferenceClick(Preference preference) {
        if (mNoContact) {
          // No contact selected yet: skip the contact screen and go directly to the pick contact activity.
          openContactScreen();
          launchPickContact();
          return true;
        }
        return false;
      }
    });
    mContact = findPreference(CONTACT_KEY);
    mContact.setOnPreferenceClickListener(new OnPreferenceClickListener() {
      @Override
      public boolean onPreferenceClick(Preference preference) {
        launchPickContact();
        return true;
      }
    });
    mContactMethodsGroup = (PreferenceCategory) findPreference(CONTACT_METHODS_GROUP);

    if (!mPreferences.contains(TEXT_SOURCE_KEY)) {
      mTextSource.setDefaultValue("Contact");
    }
    
    // Make sure "text_before" is not null before displaying the contact, i.e. before building the info message.
    if (!mPreferences.contains(TEXT_BEFORE_KEY)) {
      if (DEBUG) Log.d(TAG, "First run.");
      mTextBefore.setText(getResources().getString(R.string.default_message_header));
    }

    // Display the contact if one is selected or display a hint and disable custom texts otherwise.
    String contact = mPreferences.getString(CONTACT_KEY, null);
    String textMessage = mPreferences.getString(TEXT_MESSAGE_KEY, null);
    mNoContact = contact == null;
    mNoTextMessage = textMessage == null;
    
    if (mNoContact && mNoTextMessage) {
      if (DEBUG) Log.d(TAG, "No contact selected. Displaying hint.");
      mPreviewGroup.setTitle(getResources().getString(R.string.hint_group));
      mMessagePreview.setTitle(getResources().getString(R.string.hint));
    } else {
    	if (useContactSource()) {
        displayContact(Uri.parse(contact), false); 	
    	} else {
        buildInfoMessage();
      }
    }
    


    togglePreferenceVisibility();

    // Only register the preference change listener after everything else is set up or we'll get NPEs.
    mPreferenceChangeListener = new PreferenceChangeListener();
    mPreferences.registerOnSharedPreferenceChangeListener(mPreferenceChangeListener);
  }
  

  /**
   * Display warning message if this is an HTC build.
   */
  @Override
  protected void onStart() {
    super.onStart();

    boolean mightBeHTCBuild = android.os.Build.MANUFACTURER.contains("HTC");
    boolean preEclair = android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ECLAIR;
    if (mightBeHTCBuild && preEclair) {
      showDialog(CRASH_WARNING_DIALOG);
    } else {
      notifyService();
    }
  }

  @Override
  protected Dialog onCreateDialog(int id) {
    switch (id) {
      case CRASH_WARNING_DIALOG:
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setMessage("Older HTC Android builds can crash the device when running Contact Owner.\n" +
                           "\n" +
                           "Your phone might be running such a build. Are you sure you want to continue?")
               .setCancelable(false)
               .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                  @Override
                  public void onClick(DialogInterface dialog, int which) {
                    notifyService();
                    dialog.cancel();
                  }
               })
               .setNegativeButton("No", new DialogInterface.OnClickListener() {
                  @Override
                  public void onClick(DialogInterface dialog, int which) {
                    InfoSetup.this.finish();
                  }
               });
        return builder.create();
      default:
        if (DEBUG) Log.d(TAG, "Don't know how to create dialog " + id);
        return null;
    }
  }

  /**
   * Unregister all listeners. Unregistering {@link #mPreferenceChangeListener} is most important because otherwise we
   * end up with multiple change listeners and everything gets slowed down.
   */
  @Override
  protected void onDestroy() {
    if (DEBUG) Log.d(TAG, "onDestroy: isFinishing=" + isFinishing());
    mPreferences.unregisterOnSharedPreferenceChangeListener(mPreferenceChangeListener);
    mPreferenceChangeListener = null;
    mContactScreen.setOnPreferenceClickListener(null);
    mContact.setOnPreferenceClickListener(null);
    super.onDestroy();
  }

  /**
   * InfoService is notified of changes via startService().
   */
  private void notifyService() {
    Context context = getApplicationContext();
    Intent service = new Intent(InfoService.ACTION_INFO_MESSAGE_CHANGED, null, context, InfoService.class);
    context.startService(service);
  }

  /** Launches an external pick contact activity with the REQUEST_CODE_PICK_CONTACT request code. */
  private void launchPickContact() {
    Intent i = new Intent(android.content.Intent.ACTION_PICK, People.CONTENT_URI);
    startActivityForResult(i, REQUEST_CODE_PICK_CONTACT);
  }

  /** Debug helper that clears the shared preferences, useful for testing. */
  private void clearPreferences() {
    assert DEBUG;
    SharedPreferences.Editor editor = mPreferences.edit();
    editor.clear();
    editor.commit();
  }

  /**
   * Ugly hack to open directly the contact screen. It causes some weird behavior: pushing the Home button from the
   * contact screen and then opening the setup again results in not going to the contact screen (because the dialog
   * is non-null but single top clears the stack).
   */
  private void openContactScreen() {
    if (DEBUG) Log.d(TAG, "Displaying contact screen.");
    if (mContactScreen.getDialog() != null) {
      if (DEBUG) Log.d(TAG, "Contact screen already displayed.");
      return;
    }
    ListAdapter adapter = mMainScreen.getRootAdapter();
    for (int i = 0; i < adapter.getCount(); ++i) {
      Preference tmp = (Preference) adapter.getItem(i);
      if (CONTACT_SCREEN.equals(tmp.getKey())) {
        mMainScreen.onItemClick(null, null, i, 0);
        if (DEBUG) Log.d(TAG, "Opened contact screen.");
        break;
      }
    }
  }

  /** Handles the result of REQUEST_CODE_PICK_CONTACT, updating the contact screen and message property. */
  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (resultCode != RESULT_CANCELED) {
      switch (requestCode) {
        case REQUEST_CODE_PICK_CONTACT:
          if (data == null) {
            if (DEBUG) Log.e(TAG, "No intent data returned.");
            return;
          }
          final Uri personUri = data.getData();
          if (personUri == null) {
            if (DEBUG) Log.e(TAG, "Intent data contained no person URI.");
            return;
          }
          displayContact(personUri, true);
          return;

        default:
          if (DEBUG) Log.w(TAG, "Unknown activity result: " + requestCode + ", " + resultCode + ", " + data);
          break;
      }
    }

    super.onActivityResult(requestCode, resultCode, data);
  }

  /**
   * Updates the contact and message properties and rebuilds the contact screen to display {@code personUri}.
   *
   * @param personUri      The contact to display.
   * @param contactChanged Whether the contact changed (update screen and properties) or not (only update screen).
   */
  private void displayContact(Uri personUri, boolean contactChanged) {
    if (DEBUG) Log.v(TAG, "Displaying " + personUri.toString() + ", contactChanged " + contactChanged);

    Cursor c = getContentResolver().query(personUri, null, null, null, null);
    if (c == null || !c.moveToNext()) {
      if (DEBUG) Log.e(TAG, "No such contact: " + personUri);
      c.close();
      return;
    }

    // A contact was selected: remember this and enable custom texts.
    mNoContact = false;
    mTextBefore.setEnabled(true);
    mTextAfter.setEnabled(true);

    mContact.setTitle(c.getString(c.getColumnIndexOrThrow(People.DISPLAY_NAME)));
    int personId = c.getInt(c.getColumnIndexOrThrow(People._ID));
    c.close();

    if (DEBUG) Log.v(TAG, "Disabled message updates.");
    mDisableMessageUpdates = true;
    mContactMethodsGroup.removeAll();
    boolean hasContactInformation = false;

    // Phone numbers.
    final Uri phonesUri = Uri.withAppendedPath(personUri, People.Phones.CONTENT_DIRECTORY);
    c = getContentResolver().query(phonesUri, null, null, null, People.Phones.ISPRIMARY + " desc");
    if (c == null) {
      if (DEBUG) Log.e(TAG, "Phones query returned null cursor.");
      return;
    }
    final String contactMethodPhone = getResources().getString(R.string.contact_method_phone);
    while (c.moveToNext()) {
      int phoneId = c.getInt(c.getColumnIndexOrThrow(People.Phones._ID));
      String number = c.getString(c.getColumnIndexOrThrow(People.Phones.NUMBER));
      int type = c.getInt(c.getColumnIndexOrThrow(People.Phones.TYPE));
      String label = c.getString(c.getColumnIndexOrThrow(People.Phones.LABEL));
      label = Phones.getDisplayLabel(this, type, label).toString();

      CheckBoxPreference pref = new CheckBoxPreference(this);
      pref.setTitle(number);
      pref.setSummary(String.format(contactMethodPhone, label));
      pref.setKey(PHONE_KEY_PREFIX + personId + "_" + phoneId);
      mContactMethodsGroup.addPreference(pref);
      // Only set checked state after adding to screen, otherwise state doesn't get saved.
      if (contactChanged) {
        pref.setChecked(c.getInt(c.getColumnIndexOrThrow(People.Phones.ISPRIMARY)) != 0);
      }
      hasContactInformation = true;
    }
    c.close();

    // Other contact methods.
    final Uri contactMethodsUri = Uri.withAppendedPath(personUri, People.ContactMethods.CONTENT_DIRECTORY);
    c = getContentResolver().query(contactMethodsUri, null, null, null, People.ContactMethods.ISPRIMARY + " desc");
    if (c == null) {
      if (DEBUG) Log.e(TAG, "ContactMethods query returned null cursor.");
      return;
    }

    final String contactMethodIm = getResources().getString(R.string.contact_method_im);
    final String contactMethodEmail = getResources().getString(R.string.contact_method_email);
    final String contactMethodPostal = getResources().getString(R.string.contact_method_postal);
    final String[] protocolStrings = getResources().getStringArray(android.R.array.imProtocols);
    String label, format;
    while (c.moveToNext()) {
      final int contactMethodId = c.getInt(c.getColumnIndexOrThrow(People.ContactMethods._ID));
      int kind = c.getInt(c.getColumnIndexOrThrow(People.ContactMethods.KIND));
      String data = c.getString(c.getColumnIndexOrThrow(People.ContactMethods.DATA));
      if (kind == Contacts.KIND_IM) {
        Object protocolObj = ContactMethods.decodeImProtocol(
            c.getString(c.getColumnIndexOrThrow(People.ContactMethods.AUX_DATA)));
        if (protocolObj instanceof Number) {
          int protocol = ((Number) protocolObj).intValue();
          label = protocolStrings[protocol];
        } else {
          label = (String) protocolObj;
        }
        format = contactMethodIm;
      } else {
        label = c.getString(c.getColumnIndexOrThrow(People.ContactMethods.LABEL));
        int type = c.getInt(c.getColumnIndexOrThrow(People.ContactMethods.TYPE));
        label = ContactMethods.getDisplayLabel(this, kind, type, label).toString();
        switch (kind) {
          case Contacts.KIND_PHONE:
            format = contactMethodPhone;
            break;
          case Contacts.KIND_EMAIL:
            format = contactMethodEmail;
            break;
          case Contacts.KIND_POSTAL:
            format = contactMethodPostal;
            break;
          default:
            format = "%s";
            break;
        }
      }

      CheckBoxPreference pref = new CheckBoxPreference(this);
      pref.setTitle(data);
      pref.setSummary(String.format(format, label));
      pref.setKey(CONTACT_METHOD_KEY_PREFIX + personId + "_" + contactMethodId);
      mContactMethodsGroup.addPreference(pref);
      // Only set checked state after adding to screen, otherwise state doesn't get saved.
      if (contactChanged) {
        pref.setChecked(c.getInt(c.getColumnIndexOrThrow(People.ContactMethods.ISPRIMARY)) != 0);
      }
      hasContactInformation = true;
    }
    c.close();

    if (!hasContactInformation) {
      LongTextPreference pref = new LongTextPreference(this);
      pref.setTitle(R.string.no_contact_method);
      mContactMethodsGroup.addPreference(pref);
    }

    String infoMessage = buildInfoMessage();
    if (contactChanged) {
      setInfoMessage(infoMessage, personUri.toString());
    }

    // Done rebuilding the screen, enable message updates on preference changes.
    mDisableMessageUpdates = false;
    if (DEBUG) Log.v(TAG, "Reenabled message updates.");
  }

  /**
   * Builds the info message based on the contents of the contact screen and their checked status.
   *
   * @return The new message.
   */
  private String buildInfoMessage() {
    if (mNoContact && mTextMessage.getText() == null) {
      if (DEBUG) Log.d(TAG, "buildInfoMessage: no contact selected or text entered, aborting.");
      return null;
    }
    final char NEWLINE = '\n';
    StringBuilder tmp = new StringBuilder(100);
    String textBefore = mTextBefore.getText();
    if (textBefore != null && textBefore.length() > 0) {
      tmp.append(textBefore);
      tmp.append(NEWLINE);
    }
    
    if (!useContactSource()) {
      tmp.append(mTextMessage.getText().trim());
    } else {
	  tmp.append(mContact.getTitle());
      for (int i = 0; i < mContactMethodsGroup.getPreferenceCount(); ++i) {
	    Preference pref = mContactMethodsGroup.getPreference(i);
        if (pref instanceof CheckBoxPreference && ((CheckBoxPreference) pref).isChecked()) {
	      tmp.append(NEWLINE);
          tmp.append(pref.getTitle());
        }
      }
    }
    String textAfter = mTextAfter.getText();
    if (textAfter != null && textAfter.length() > 0) {
      tmp.append(NEWLINE);
      tmp.append(textAfter);
    }

    String infoMessage = tmp.toString();
    if (DEBUG) Log.d(TAG, "New info message: " + infoMessage);
    mPreviewGroup.setTitle(getResources().getString(R.string.preview_group));
    mMessagePreview.setGravity(Gravity.CENTER_HORIZONTAL);
    mMessagePreview.setTitle(infoMessage);
    return infoMessage;
  }
  /**
   * Saves the info message and optionally the contact URI.
   *
   * @param infoMessage    The new info message to save.
   * @param contactUri The new contact URI to save or null if the contact didn't change.
   */
  private void setInfoMessage(String infoMessage, String contactUri) {
    SharedPreferences.Editor edit = mPreferences.edit();
    edit.putString(MESSAGE_KEY, infoMessage);
    if (contactUri != null) {
      if (DEBUG) Log.d(TAG, "Updating contact and info message.");
      edit.putString(CONTACT_KEY, contactUri);
    } else {
      if (DEBUG) Log.d(TAG, "Updating info message.");
    }
    edit.commit();
    notifyService();
  }
}
